package com.zhy.dao;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import com.zhy.annotation.Column;
import com.zhy.annotation.Table;

public class DaoTemplate {

    private Connection conn;
	private Statement st;
	private JdbcPoll jdbcPoll = JdbcPoll.getInstance();

	public List findByConditions(Class<?> cls, String conditions) {
		List objs = new ArrayList();
		ResultSet rs = null;
		try {

			String sql = "select * from " + getTableName(cls) + " where 1=1 "
					+ conditions;
			rs = getResultSet(sql);

			while (rs.next()) {
				objs.add(objectInit(cls, rs));
			}
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			try {
				rs.close();
				st.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			returnConnection();
		}
		return objs;
	}

	public List findAll(Class<?> cls) {
		return findByConditions(cls, "");
	}

	// 根据id

	public Object findOne(int id, Class<?> cls) {

		return findByConditions(
				cls,
				getCondition(getColumnName(getIdField(cls)), Symbol.andEq,
						String.valueOf(id))).get(0);
	}

	// 传入一个对象，根据主键查询，有数据则修改无数据则新增

	public void save(Object obj) {

		Field fs[] = obj.getClass().getDeclaredFields();
		int id = 0;
		try {
			id = getId(obj);

			String sql = "";
			st = useConnection().createStatement();
			if (id != 0) {
				sql = "update " + getTableName(obj.getClass()) + " set ";
				for (int i = 0; i < fs.length; ++i) {
					sql = sql + getColumnName(fs[i]) + "="
							+ getFildValue(fs[i], obj) + ",";

				}
				sql = sql.substring(0, sql.length() - 1);
				sql = sql + " where "
						+ getColumnName(getIdField(obj.getClass())) + "="
						+ getId(obj);
			} else {
				StringBuffer col = new StringBuffer();
				StringBuffer value = new StringBuffer();
				sql = "insert into " + getTableName(obj.getClass()) + "(";
				sql = sql + getColumnNames(fs) + ") values("
						+ getConditionValues(getDeclaredFieldsValues(obj))
						+ ")";

			}

			st.executeUpdate(sql);
			System.out.println(sql);
			commit();
		} catch (SQLException e) {
			e.printStackTrace();
			rollBack();
		} finally {
			try {
				st.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
			returnConnection();
		}

	}

	private int getId(Object obj) {

		int id = 0;
		try {
			id = (int) getIdField(obj.getClass()).get(obj);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return id;
	}

	private Object objectInit(Class<?> cls, ResultSet result) {
		Object obj = null;
		try {

			// 结果集有值，初始化obj对象
			obj = cls.newInstance();
			// 遍历该obj属性
			for (Field f : cls.getDeclaredFields()) {
				// 设置该属性可访问
				f.setAccessible(true);
				// 该属性类型若同包（都为model类）根据外键返回该model类对象
				if (f.getType().getPackage() == cls.getPackage()) {
					Object fieldObj = findOne(result.getInt(getColumnName(f)),
							f.getType());
					if (fieldObj != null)
						f.set(obj, fieldObj);
				} else {
					f.set(obj, result.getObject(getColumnName(f)));
				}

			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return obj;
	}

	private String getTableName(Class<?> cls) {

		String tableName;
		if (cls.isAnnotationPresent(Table.class))
			tableName = cls.getAnnotation(Table.class).tableName();
		else
			tableName = cls.getName();
		return tableName;
	}

	private String getColumnName(Field field) {

		String fieldName;
		if (field.isAnnotationPresent(Column.class))
			fieldName = field.getAnnotation(Column.class).columnName();
		else
			fieldName = field.getName();
		return fieldName;
	}

	public String getConditionValues(List<String> values) {

		StringBuffer value = new StringBuffer();
		for (int i = 0, j = values.size(); i < j; i++) {
			value.append("'" + values.get(i).toString() + "',");
		}
		return value.substring(0, value.length() - 1).toString();
	}

	public String getCondition(String column, Symbol symbol, String value) {

		StringBuffer condition = new StringBuffer();
		condition.append(symbol.getType());
		if ("in".equals(symbol.getValue()))
			condition.append(column + "in (" + value + ") ");
		else
			condition.append(column + symbol.getValue() + value + " ");

		return condition.toString();
	}

	private String getColumnNames(Field[] fields) {

		StringBuffer sb = new StringBuffer();
		for (int i = 0, j = fields.length; i < j; i++) {
			if (fields[i].getAnnotation(Column.class).identify())
				continue;
			sb.append(getColumnName(fields[i]) + ",");
		}

		return sb.substring(0, sb.length() - 1).toString();
	}

	private List<String> getDeclaredFieldsValues(Object obj) {

		Field[] fields = obj.getClass().getDeclaredFields();
		List<String> values = new ArrayList<String>();
		for (int i = 0; i < fields.length; i++) {
			if (fields[i].getAnnotation(Column.class).identify())
				continue;
			try {
				fields[i].setAccessible(true);
				if (fields[i].getType().getPackage() == obj.getClass()
						.getPackage()) {
					values.add(getId(fields[i].get(obj)) + "");
				} else {

					values.add(fields[i].get(obj).toString());
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		return values;
	}

	private Object getFildValue(Field field, Object obj) {

		Object object = null;
		try {
			if (field.getType().getPackage() == obj.getClass().getPackage()) {

				object = getId(field.get(obj));

			} else {
				object = field.get(obj);
			}
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}

		return object;
	}

	private Field getIdField(Class<?> cls) {

		Field field = null;
		Field[] fs = cls.getDeclaredFields();
		for (int i = 0, j = fs.length; i < j; ++i) {
			fs[i].setAccessible(true);
			if (!fs[i].isAnnotationPresent(Column.class))
				continue;
			if (fs[i].getAnnotation(Column.class).identify()) {

				field = fs[i];

				break;
			}
		}

		return field;
	}

	protected Connection useConnection() {

		while (conn == null) {
			conn = jdbcPoll.useConnection();
		}

		return conn;
	}

	protected void returnConnection() {
		jdbcPoll.returnConnection(conn);
		conn = null;
	}

	public void commit() {
		try {
			conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}

	public void rollBack() {
		try {
			conn.rollback();
		} catch (SQLException e) {
			e.printStackTrace();
		}
	}
	

	public ResultSet getResultSet(String sql) throws SQLException {
		st = useConnection().createStatement();
		ResultSet rs = st.executeQuery(sql);
		st.close();
		returnConnection();
		System.out.println(rs);
		return rs;
	}

	

}

class JdbcPoll {

	private static JdbcPoll poll;
	private static String driver;
	private static String url;
	private static String userName;
	private static String password;
	private static List<Connection> connections;
	private static int jdbcPollInitSize;
	private static int jdbcPollMaxSize;
	private static int connectionSize = 0;
	static {
		InputStream is = DaoTemplate.class.getClassLoader()
				.getResourceAsStream("jdbc.properties");

		Properties p = new Properties();
		try {
			p.load(is);
			driver = p.getProperty("driver");
			url = p.getProperty("url");
			userName = p.getProperty("username");
			password = p.getProperty("userps");
			jdbcPollInitSize = Integer.parseInt(p
					.getProperty("jdbcPollInitSize"));
			jdbcPollMaxSize = Integer
					.parseInt(p.getProperty("jdbcPollMaxSize"));
			Class.forName(driver);
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
	}

	private JdbcPoll() {
		init();
	}

	private void init() {
		connections = new ArrayList<Connection>(jdbcPollInitSize);
		for (int i = 0; i < jdbcPollInitSize; i++) {
			connections.add(getConnection());
		}

	}

	public static synchronized JdbcPoll getInstance() {
		return poll == null ? poll = new JdbcPoll() : poll;
	}

	protected synchronized Connection useConnection() {
		Connection conn = null;
		synchronized (this) {
			if (connectionSize >= jdbcPollInitSize
					&& connectionSize <= jdbcPollMaxSize) {
				conn = getConnection();
				++connectionSize;
			} else if (connectionSize < jdbcPollInitSize) {
				conn = connections.get(connectionSize);
				connections.remove(conn);
				++connectionSize;
			} else {
				System.out.println("connectionSize = jdbcPollMaxSize ! ");
			}
		}
		return conn;
	}

	protected synchronized void returnConnection(Connection conn) {
		if (connectionSize > jdbcPollInitSize) {
			try {
				conn.close();

			} catch (SQLException e) {
				e.printStackTrace();
			}
		} else {
			connections.add(conn);
		}
		--connectionSize;
	}

	private Connection getConnection() {

		Connection conn = null;
		try {
			conn = DriverManager.getConnection(url, userName, password);
			conn.setAutoCommit(false);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return conn;
	}

}

enum Symbol {
	andEq(" and ", "="),
	andNeq(" and ", "<>"),
	andLt(" and ", "<"),
	andLte(" and ", "<="),
	andGt(" and ", ">"),
	andGte(" and ", ">="),
	andIn(" and ", "in"),
	orEq(" or ", "="),
	orNeq(" or ", "<>"),
	orLt(" or ", "<"),
	orLte(" or ", "<="),
	orGt(" or ", ">"), 
	orGte(" or ", ">="),
	orIn(" or ", "in");
	private String value;
	private String type;

	public String getValue() {
		return value;
	}
	public String getType() {
		return type;
	}

	Symbol(String type, String value) {
		this.value = value;
	}

}
